package org.easydev.base.distributedutil.lock.redis;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

@Component
public class RedisDistributedLock {

    @Autowired
    RedissonClient redissonClient;

    private final StringRedisTemplate redisTemplate;

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate) {
        this.redisTemplate = stringRedisTemplate;
    }


    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁的 key
     * @param expireTime 锁自动过期时间（秒）
     * @return 返回唯一标识（用于后续解锁），null 表示获取失败
     */
    public String tryLock(String lockKey, long expireTime) {
        String value = UUID.randomUUID().toString();
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success) ? value : null;
    }

    /**
     * 安全释放锁
     * @param lockKey 锁的key
     * @param value 加锁成功后返回的value
     * @return  是否释放成功
     */
    public boolean unlock(String lockKey, String value) {
        // 使用Lua脚本保证执行的原子性
        String script = """
                if redis.call('get', KEYS[1]) == ARGV[1] then
                    return redis.call('del', KEYS[1])
                else
                    return 0
                end
                """;
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
        return result != null && result == 1L;
    }

    /**
     * Redisson的分布式锁就不用包装了，直接让前台调用Redisson即可。这里暂且示例
     * @param lockKey
     * @param waitTime
     * @return
     * @throws InterruptedException
     */
    public Lock tryLockByRedisson(String lockKey, long waitTime) throws InterruptedException {
        String key = "lock:" + lockKey;
        RLock lock = redissonClient.getLock(key);
        try {
            // 尝试获取锁：最多等待 10 秒，10 秒内获取到锁后，自动续期（默认 30 秒）
            // 参数含义：waitTime（等待时间）、leaseTime（锁持有时间，-1 表示自动续期）、时间单位
            boolean isLocked = lock.tryLock(waitTime, -1, TimeUnit.SECONDS);
            if (isLocked) {
                return lock;
            }else {
                return null;
            }
        } catch (InterruptedException e) {
            throw e;
        }
    }

    public void unlockByRedisson(Lock lock) {
        lock.unlock();
    }

}
